/*
 * Copyright 2012-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.web.embedded.tomcat;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Manager;
import org.apache.catalina.Valve;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot.ResourceSetType;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.catalina.util.LifecycleBase;
import org.apache.catalina.util.SessionConfig;
import org.apache.catalina.webresources.AbstractResourceSet;
import org.apache.catalina.webresources.EmptyResource;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http2.Http2Protocol;
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.scan.StandardJarScanFilter;

import org.springframework.boot.util.LambdaSafe;
import org.springframework.boot.web.server.Cookie.SameSite;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.MimeMappings;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.NativeDetector;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
 * {@link AbstractServletWebServerFactory} that can be used to create
 * {@link TomcatWebServer}s. Can be initialized using Spring's
 * {@link ServletContextInitializer}s or Tomcat {@link LifecycleListener}s.
 * <p>
 * Unless explicitly configured otherwise this factory will create containers that listen
 * for HTTP requests on port 8080.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Brock Mills
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Eddú Meléndez
 * @author Christoffer Sawicki
 * @author Dawid Antecki
 * @since 2.0.0
 * @see #setPort(int)
 * @see #setContextLifecycleListeners(Collection)
 * @see TomcatWebServer
 */
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

	private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

	private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();

	/**
	 * The class name of default protocol used.
	 */
	public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";

	private File baseDirectory;
	//Valve是一个处理请求的组件；一系列的Valve关联起来组成一个Pipeline；真正的执行业务在invoke方法中
	private List<Valve> engineValves = new ArrayList<>();

	private List<Valve> contextValves = new ArrayList<>();

	private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();

	private List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();

	private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();

	private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();

	private Set<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = new LinkedHashSet<>();

	private final List<Connector> additionalTomcatConnectors = new ArrayList<>();

	private ResourceLoader resourceLoader;
    //默认使用的是 同步非阻塞io协议
	private String protocol = DEFAULT_PROTOCOL;

	private Set<String> tldSkipPatterns = new LinkedHashSet<>(TldPatterns.DEFAULT_SKIP);

	private Set<String> tldScanPatterns = new LinkedHashSet<>(TldPatterns.DEFAULT_SCAN);

	private Charset uriEncoding = DEFAULT_CHARSET;

	private int backgroundProcessorDelay;

	private boolean disableMBeanRegistry = true;

	/**
	 * Create a new {@link TomcatServletWebServerFactory} instance.
	 */
	public TomcatServletWebServerFactory() {
	}

	/**
	 * Create a new {@link TomcatServletWebServerFactory} that listens for requests using
	 * the specified port.
	 * @param port the port to listen on
	 */
	public TomcatServletWebServerFactory(int port) {
		super(port);
	}

	/**
	 * Create a new {@link TomcatServletWebServerFactory} with the specified context path
	 * and port.
	 * @param contextPath the root context path
	 * @param port the port to listen on
	 */
	public TomcatServletWebServerFactory(String contextPath, int port) {
		super(contextPath, port);
	}

	private static List<LifecycleListener> getDefaultServerLifecycleListeners() {
		ArrayList<LifecycleListener> lifecycleListeners = new ArrayList<>();
		if (!NativeDetector.inNativeImage()) {
			AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
			if (AprLifecycleListener.isAprAvailable()) {
				lifecycleListeners.add(aprLifecycleListener);
			}
		}
		return lifecycleListeners;
	}

	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		// 默认情况下禁用Tomcat的MBean注册表，会节省大约2MB的堆 而Mbean的作用就是tomcat度量采集和监控
		// JMX是Java平台的一组标准，提供了用于监控和管理Java应用程序的API。它允许应用程序中的各个组件（如Tomcat）通过注册MBean来提供管理接口，
		// 这些接口可以通过远程或本地的JMX客户端进行访问（如JConsole、VisualVM或JMC 通过这些客户端工具可以监控和管理Tomcat服务器的功能。可以查看性能指标、执行操作（如重新加载应用程序、垃圾回收等）、配置参数等。）
		// 关闭JMX并不一定会直接提高性能，而是减少了一些开销和资源消耗，特别是在对于简单的应用或者不需要远程管理和监控的场景
		// Spring Boot默认不启用JMX功能是为了简化配置和减少复杂性，以提供开箱即用的应用程序开发体验。远程管理和监控通常由专门的运维团队或系统管理工具处理，而不是应用程序自身
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		// 实例化一个Tomcat【相当于tomcat源码中的catalina】 每个Service只能包含一个Container（Servlet）容器, 表示一个特定Service的请求处理流水线。做为一个Service可以有多个
		// connector连接器，引擎（Engine）从连接器接收和处理所有的请求，将响应返回给适合的连接器，通过连接器传输给用户。
		// 在所创建的那一个Tomcat Engine上创建了一个Tomcat Host 。每个Virtual Host虚拟主机和某个网络域名Domain Name相匹配,每个虚拟主机下都可以部署(deploy)一个
		// 或者多个Web App，每个Web App对应于一个Context，有一个Context path。当Host获得一个请求时，将把该请求匹配到某个Context上。 一个 Tomcat Engine 上面可以有多个 Tomcat Host。
		Tomcat tomcat = new Tomcat();
		// 如果设置了baseDirectory则使用之，否则，在文件系统当前用户的临时目录下创建基础工作目录，缺省情况下，baseDirectory 是没有被设置的，新建的临时目录类似于 ：
		//C:\Users\Lenovo\AppData\Local\Temp\tomcat.8081.603668103130407998
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		//设置tomcat临时工作目录
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		// 将LifecycleEvent侦听器添加到此Server组件中
		for (LifecycleListener listener : this.serverLifecycleListeners) {
			//注意：tomcat.getServer()如果执行那么会在此处创建server和service（默认不执行）
			tomcat.getServer().addLifecycleListener(listener);
		}
		//创建tomcat的Connector(tomcat主要由connector连接器(连接客户端请求)和容器组成)并进行配置,缺省协议为org.apache.coyote.http11.Http11NioProtocol，表示处理http v1.1协议
		// Connector启动的核心动作是协议处理器（protocolHandler）的启动 对于缺省配置的springboot web应用，它会在8080端口提供 HTTP 服务，所以这里是一个处理http协议请求的Http11NioProtocol实例，
		// 使用nio方式处理 http 协议，Connector 对HTTP请求的接收和处理并不是亲自完成的，而是交给该 Http11NioProtocol protocolHandler 完成，而 protocolHandler又进一步将请求处理工作交给 NioEndpoint 完成。
		// 调用链 :Connector.start()=> startInternal() => Http11NioProtocol protocolHandler.start()=>endpoint.start();
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		// 给service添加Connector，这里getServer()会触发tomcat内部建立一个StandardServer实例,停止端口使用8005{server用来管理整个tomcat},
		// 新建的StandardServer实例内含一个新建的StandardService实例,名称为Tomcat;
		tomcat.getService().addConnector(connector);
		// 对我们的connector进行属性的配置 我们可以通过TomcatProtocolHandlerCustomizer和TomcatConnectorCustomizer对其进行扩展
		// 根据配置参数定制 connector 端口，uri encoding字符集，是否启用SSL, 是否使用压缩等 缺省情况下端口是 8080, uri encoding 是 utf-8
		customizeConnector(connector);
		//将Connector绑定到service中{因为在customizeConnector中可以自定义connector,所以为了避免更换connector的情况，这里会获取service中的connector然后跟当前的比对，如果不相等就进行替换}
		tomcat.setConnector(connector);
		//关闭热部署，另外在实例化二者的同时，其通过pipeline.setBasic方法初始化了管道中的value基础对象
		// 这里 getHost():
		//(1)会触发tomcat内部建立一个StandardEngine实例,名称为Tomcat;新建的StandardEngine实例缺省使用localhost:8080;新建的StandardEngine实例会设置成上面新建service的关联container;
		//(2)这里getHost()也会触发tomcat内部建立一个StandardHost实例,名称为localhost,新建的StandardHost实例会设置成上面新建engine的子container
		tomcat.getHost().setAutoDeploy(false);
		//配置tomcat engine,主要是为其增加Valve : this.engineValves （缺省没有）
		configureEngine(tomcat.getEngine());
		// 如果指定了更多附加的Tomcat Connector，也添加进来{http,https,ajp等}
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		// 准备TomcatEmbeddedContext，对应一个webapp,并将其通过host关联到tomcat
		prepareContext(tomcat.getHost(), initializers);
		// 实例化TomcatWebServer，将DispatcherServlet以及一些Filter添加到Tomcat中
		return getTomcatWebServer(tomcat);
	}
	// value对象在Tomcat的各级容器中都有标准类型，并且各级容器都有一个pipeline{即通过new创建各级容器时}，在请求处理过程中会从各级的第一个value对象开始依次执行一遍，
	// value用于加入到对应的各级容器的逻辑，默认缺省有一个标注value(接口)实现，名字为StandardEnginValue，StandardHostValue，StandardContextValve，StandardWrapperValve等
	// Pipeline是valve的一个集合，valve.invoke()方法的执行是顺序执行的，执行完当前之后就调用下一个valve.invoke()方法，直到执行到最后一个valve为止
	//请求流转过程：每个容器包含一个pipeline，每个pipeline包含一个valve集合，位于前面的的valve做完业务处理后将调用后面的valve做业务处理，而容器的缺省valve位于集合的
	// 最后一个位置，负责调用下层容器的pipeline的第一个valve做业务处理，调用会从Engine的第一个valve调用开始，一直执行到调用Wrapper的缺省valve：StandardWrapperValve，
	// 而filter与servlet的处理就是在这个valve之后进行的，最后执行到对应的Servlet.service()执行真正的业务处理 。Engine的第一个valve是由connector中的Adapter调用的
	private void configureEngine(Engine engine) {
		//设置引擎的后台进程延迟(TomcatWebServerFactoryCustomizer中对serverProperties解析初始化  默认为10),在engine start完成后会开启一个定时任务
		engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
		//将引擎的Value对象注册给引擎的pipeline，每个容器的pipeline都有一个basic value的实现类：StandardEnginValue等
		for (Valve valve : this.engineValves) {
			engine.getPipeline().addValve(valve);
		}
	}
    //Tomcat的项目部署方式(外部容器):1.直接把项目复制到Tomcat安装目录的webapps目录中，这是最简单的一种Tomcat项目部署的方法(war包或者编译后的文件夹)
	//2:在tomcat安装目录中有一个conf文件夹，打开此文件夹，其中包含配置文件server.xml，打开配置文件，并在<host>和</host>之间插入如下语句。
	//<Context path="/hello" docBase="F:\eclipse3.2\workspace\hello\WebRoot" debug="0" privileged="true"></Context>其中，docBase为设置项目的路径。
	//3:在conf目录中，在Catalina\localhost（此处需要注意目录的大小写）目录下新建一个XML文件，任意命名，只要和当前文件中的文件名不重复即可，代码如下。
	//<Context path="/hello" docBase="D:\eclipse3.2\workspace\hello\WebRoot" debug="0" privileged="true"> </Context>
	//其中第三种方法相对来说比较灵活，并且可以设置别名(copyxml属性为true时且项目内有context.xml，当第一次启动后会把该文件复制到该目录下，之后的启动均会采用该方式，优先级最高)
	protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
		// 准备Host的docBase，在Host中还有一个appBase属性(默认为webapps) 两者可以结合使用
		/* <host appBase=”webapps”>
 			<Context docBase=”/test”> </Context>
 		   </host>
 		appBase,这个目录表示：1 这个目录下面的子目录将自动被部署为应用。 2 这个目录下面的.war文件将被自动解压缩并部署为应用
 		而docBase只是指向了你某个应用的目录，这个可以和appBase没有任何关系，相对路径时会和appBase进行拼接 如果是绝对路径，那么两者无任何关系
 		docBase=“test”， 访问的是 d:\tomcat\webapps\test， appBase为根目录；
		docBase="\test"， 访问的是 d:\tomcat\webapps\test，appBase为根目录；
		docBase=“d:\test”， 访问的是 d:\test，appBase无效；*/
		File documentRoot = getValidDocumentRoot();
		// 创建TomcatEmbeddedContext实例其实现了StandardContext, 这个context对应一个web应用，也是整个Spring Boot Servlet应用中唯一的一个web应用
		// 以下创建和初始化一个TomcatEmbeddedContext的过程，可以认为是往tomcat servlet容器中部署和启动一个web应用的过程，只不过在传统方式下，
		// 一个web应用部署到tomcat使用war包的方式，而这里是完全程序化的方式。
		TomcatEmbeddedContext context = new TomcatEmbeddedContext();
		//documentRoot缺省为null
		if (documentRoot != null) {
			context.setResources(new LoaderHidingResourceRoot(context));
		}
		//设置context名称为属性contextPath,缺省为 “”,可配置:server.Servlet.context-Path
		context.setName(getContextPath());
		//设置context显示名称为属性displayName,缺省为 “application”，可配置 server.Servlet.applicationDisplayName
		context.setDisplayName(getDisplayName());
		//设置context路径为属性contextPath,缺省为"",即访问路径
		context.setPath(getContextPath());
		// 确定Tomcat Web服务器的文档根目录，以便将应用程序的静态资源和WAR文件的解压内容放置在其中。如果未显式配置文档根目录，则会使用临时目录来确保应用程序之间的隔离性。
		// 新建的docBase的目录类似于:C:\Users\Lenovo\AppData\Local\Temp\tomcat-docbase.8081.14172785758451886833
		// !!! 注意这里的docBase和上面提到的baseDir(tomcat工作临时目录)不同
		File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
		//设置web应用程序的文档根
		context.setDocBase(docBase.getAbsolutePath());
		// 将LifecycleEvent侦听器添加到组件中，这个监听用于设置context的配置(对三大组件实现类上的注解进行处理)以及是否加入登录验证的逻辑
		// 但是context组件在发布configure_start事件类型之前还未添加三大组件，所以默认情况下该监听器无用 除非在该事件发布之前进行了自定义添加了三大组件
		// 外置tomcat是在解析完web.xml后进行的该操作
		context.addLifecycleListener(new FixContextListener());
		//为此web应用程序设置父类加载器，将AnnotationConfigServletWebServerApplicationContext对应的加载器设置到context中
		context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
				: ClassUtils.getDefaultClassLoader());
		//增加缺省的locale encoding mapping参数映射集合 : 英语/UTF-8, 法语/UTF-8
		resetDefaultLocaleMapping(context);
		//增加自定义的参数映射集合
		addLocaleMappings(context);
		try {
			//表示Tomcat应该为上传文件创建临时目录。这对于处理文件上传的Web应用程序是非常有用的。当文件上传完成后，可以从临时目录中将文件移动到适当的位置，以供应用程序进一步处理
			context.setCreateUploadTargets(true);
		}
		catch (NoSuchMethodError ex) {
			// Tomcat is < 8.5.39. Continue.
		}
		//设置扫描TLD文件(标签库(jsp中的各种标签)描述文件，用于存放标签名字和类的映射用的)时要扫描的和跳过的JAR文件名列表
		configureTldPatterns(context);
		// 注意这里创建的WebappLoader并不是真正的类加载器而是类载入器，Webapp类加载器则是在Context容器启动时候由WebappLoader初始化，Webapp类加载器的父类加载器是
		// Tomcat容器在初始化阶段通过反射设置的，一个web容器可能需要部署两个应用程序，不同的应用程序可能会依赖同一个第三方类库的不同版本，不能要求同一个类库在同
		// 一个服务器只有一份，因此要保证每个应用程序的类库都是独立的，保证相互隔离。所以每个应用需要自身的Webapp类加载器。
		WebappLoader loader = new WebappLoader();
		//设置loaderclass(Webapp类加载器)
		loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
		//true代表遵守双亲委派机制(springboot本身就一个应用不需要区分不同的版本信息，所以其默认遵守双亲委派机制有助于简化应用程序的部署和维护)
		loader.setDelegate(true);
		context.setLoader(loader);
		//添加缺省Servlet : org.apache.catalina.servlets.DefaultServlet
		if (isRegisterDefaultServlet()) {
			// 缺省情况下,不会注册Tomcat的DefaultServlet,它具有处理默认请求的重要功能。它的主要作用是为那些没有被显式映射到其他Servlet的请求提供服务，
			// 通常用于处理静态资源(HTML、CSS、JavaScript文件)
			addDefaultServlet(context);
		}
		//检测是否需要添加Jsp有关Servlet : 缺省没有引用Jsp有关包，因此不使用Jsp也不添加该Servlet
		if (shouldRegisterJspServlet()) {
			// Spring boot提供了一个工具类org.springframework.boot.context.embedded.JspServlet
			// 检测类org.apache.jasper.servlet.JspServlet是否存在于classpath中，如果存在，则认为应该注册JSP Servlet。
			// 缺省情况下,不注册(换句话讲,Springboot web应用缺省不支持JSP)  注意 !!! 这一点和使用Tomcat充当外部容器的情况是不一样的，使用Tomcat作为外部容器的时候，
			// JSP Servlet缺省是被注册的。如果想在Spring boot中支持JSP，则需要将tomcat-embed-jasper包加入classpath中。
			addJspServlet(context);
			// Jasper把JSP文件解析成java文件，然后编译成JVM可以使用的class文件。有很多的JSP解析引擎，Tomcat中使用的是Jasper。添加的Jasper initializer用于初始化jasper。
			addJasperInitializer(context);
		}
		// 将LifecycleEvent侦听器添加到组件中,这个监听器主要把BOOT-INF/classes下的资源加入tomcat的ResourceSet(参考：https://www.jb51.net/article/141474.htm)
		context.addLifecycleListener(new StaticResourceConfigurer(context));
		//组合SpringBoot需要初始化的所有 ServletContextInitializer
		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
		//将上面context设置为host的child , (也是唯一的一个child)，所以在start过程中获取host组件children时不再为null，而外置tomcat在host是没有设置的，
		// 部署完成的时候才设置了层级关系  此处这样设置相当于在server.xml中定义了context标签
		host.addChild(context);
		// 配置context:1.将SpringBoot自定义的SCI封装到TomcatStarter(jakarta.servlet.ServletContainerInitializer实现类)中
		// 2.将spring领域的MIME映射配置设置到context中去
		// 3.将spring领域的session配置设置到context中去，比如 sessionTimeout
		configureContext(context, initializersToUse);
		// 内部实现为空,可自定义该方法，在tomcat启动之前对配置好的context进行扩展
		postProcessContext(context);
	}

	/**
	 *  覆盖Tomcat的默认区域设置映射以与其他服务器对齐
	 * Override Tomcat's default locale mappings to align with other servers. See
	 * {@code org.apache.catalina.util.CharsetMapperDefault.properties}.
	 * @param context the context to reset
	 */
	private void resetDefaultLocaleMapping(TomcatEmbeddedContext context) {
		context.addLocaleEncodingMappingParameter(Locale.ENGLISH.toString(), DEFAULT_CHARSET.displayName());
		context.addLocaleEncodingMappingParameter(Locale.FRENCH.toString(), DEFAULT_CHARSET.displayName());
	}

	private void addLocaleMappings(TomcatEmbeddedContext context) {
		getLocaleCharsetMappings().forEach(
				(locale, charset) -> context.addLocaleEncodingMappingParameter(locale.toString(), charset.toString()));
	}

	private void configureTldPatterns(TomcatEmbeddedContext context) {
		//创建了一个Jar扫描过滤器的实例
		StandardJarScanFilter filter = new StandardJarScanFilter();
		//扫描TLD文件时要跳过JAR文件名列表，用逗号分隔,(用于指定哪些JAR文件中的TLD文件不应被扫描。这可以用于优化性能，避免不必要的扫描)
		filter.setTldSkip(StringUtils.collectionToCommaDelimitedString(this.tldSkipPatterns));
		//扫描TLD文件时要扫描的JAR文件名列表，用逗号分隔(用于指定哪些JAR文件中的TLD文件应该被扫描)
		filter.setTldScan(StringUtils.collectionToCommaDelimitedString(this.tldScanPatterns));
		//将创建的Jar扫描过滤器配置到ServletContext的Jar扫描器中。这样配置后，Servlet容器在启动时会使用这个过滤器来决定哪些JAR文件中的TLD文件需要被扫描，哪些不需要
		context.getJarScanner().setJarScanFilter(filter);
	}

	private void addDefaultServlet(Context context) {
		//每个servlet在容器中均是一个个wrapper
		Wrapper defaultServlet = context.createWrapper();
		defaultServlet.setName("default");
		defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
		defaultServlet.addInitParameter("debug", "0");
		defaultServlet.addInitParameter("listings", "false");
		defaultServlet.setLoadOnStartup(1);
		// Otherwise the default location of a Spring DispatcherServlet cannot be set
		defaultServlet.setOverridable(true);
		context.addChild(defaultServlet);
		//当前servlet匹配的路径信息
		context.addServletMappingDecoded("/", "default");
	}

	private void addJspServlet(Context context) {
		Wrapper jspServlet = context.createWrapper();
		jspServlet.setName("jsp");
		jspServlet.setServletClass(getJsp().getClassName());
		jspServlet.addInitParameter("fork", "false");
		getJsp().getInitParameters().forEach(jspServlet::addInitParameter);
		jspServlet.setLoadOnStartup(3);
		context.addChild(jspServlet);
		//servlet匹配的路径信息
		context.addServletMappingDecoded("*.jsp", "jsp");
		context.addServletMappingDecoded("*.jspx", "jsp");
	}

	private void addJasperInitializer(TomcatEmbeddedContext context) {
		try {
			ServletContainerInitializer initializer = (ServletContainerInitializer) ClassUtils
					.forName("org.apache.jasper.servlet.JasperInitializer", null).getDeclaredConstructor()
					.newInstance();
			context.addServletContainerInitializer(initializer, null);
		}
		catch (Exception ex) {
			// Probably not Tomcat 8
		}
	}

	// Needs to be protected so it can be used by subclasses
	protected void customizeConnector(Connector connector) {
		// 根据配置设置监听端口 (对应配置项:server.port,缺省值8080) 后置处理器会调用ServletWebServerFactoryCustomizer.customize()
		// 将配置文件中的值赋值给AbstractConfigurableWebServerFactory中的属性  注意：我们平时设置的端口其实就是给connector设置的
		int port = Math.max(getPort(), 0);
		connector.setPort(port);
		//设置服务器响应头的值
		if (StringUtils.hasText(getServerHeader())) {
			connector.setProperty("server", getServerHeader());
		}
		//该服务绑定IP地址，启动服务器时如本机不是该IP地址则抛出异常启动失败，只有特殊需求的情况下才配置
		if (connector.getProtocolHandler() instanceof AbstractProtocol) {
			customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
		}
		//处理TomcatProtocolHandlerCustomizers,自定义协议处理器
		invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
		//根据配置设置URL encoding
		if (getUriEncoding() != null) {
			connector.setURIEncoding(getUriEncoding().name());
		}
		// Don't bind to the socket prematurely if ApplicationContext is slow to start
		// 如果ApplicationContext启动缓慢，不要过早绑定到套接字(是一种通信机制,通信的两方的一种约定,socket屏蔽了各个协议的通信细节，
		// 提供了tcp/ip协议的抽象，对外提供了一套接口，通过这个接口就可以统一、方便的使用tcp/ip协议的功能 tcp/udp的编程式接口),而该属性设置为false的话意味着在
		//connector初始化的过程中不会初始化serverSocketChannel对象 而是在其start的过程中来创建初始化该对象   外置tomcat默认情况下在init过程中初始化了serverSocketChannel对象
		connector.setProperty("bindOnInit", "false");
		//设置将应用于服务器的HTTP/2相关配置(升级协议)
		if (getHttp2() != null && getHttp2().isEnabled()) {
			connector.addUpgradeProtocol(new Http2Protocol());
		}
		//根据配置设置SSL,安全传输协议(对应配置项:server.ssl.*)
		if (getSsl() != null && getSsl().isEnabled()) {
			customizeSsl(connector);
		}
		//根据配置进行压缩设置,以节省空间或者提升传播速度 (对应配置项:server.compression.enabled,缺省值false 开启的话会增加cpu和资源消耗，但是会提升传播速度)
		TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
		//根据配置使用一个CompressionConnectorCustomizer进行定制  该自定义对象没有注入到ioc容器中，所以此处通过手动创建调用
		compression.customize(connector);
		// 处理TomcatConnectorCustomizer，该集合以及TomcatProtocolHandlerCustomizers都是在TomcatServletWebServerFactory
		// bean对象实例化的时候通过属性注入进行的赋值
		for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
			customizer.customize(connector);
		}
	}

	private void customizeProtocol(AbstractProtocol<?> protocol) {
		//如果配置文件中的server.address不为空
		if (getAddress() != null) {
			protocol.setAddress(getAddress());
		}
	}

	@SuppressWarnings("unchecked")
	private void invokeProtocolHandlerCustomizers(ProtocolHandler protocolHandler) {
		LambdaSafe.callbacks(TomcatProtocolHandlerCustomizer.class, this.tomcatProtocolHandlerCustomizers,
				protocolHandler).invoke((customizer) -> customizer.customize(protocolHandler));
	}

	private void customizeSsl(Connector connector) {
		new SslConnectorCustomizer(getSsl(), getSslStoreProvider()).customize(connector);
	}

	/**
	 * Configure the Tomcat {@link Context}.
	 * @param context the Tomcat context
	 * @param initializers initializers to apply
	 */
	protected void configureContext(Context context, ServletContextInitializer[] initializers) {
		//TomcatStarter是SpringBoot对于嵌入式Tomcat的定义，实现了ServletContainerInitializer，注意这里将传入的initializers集合作为构造参数封装到了TomcatStarter
		//类中initializers属性中，之后又把TomcatStarter封装到了context中，当context启动的时候会调用TomcatStarter中的onStartup方法，然后遍历循环处理initializers集合
		TomcatStarter starter = new TomcatStarter(initializers);
		//默认创建的就是TomcatEmbeddedContext对象
		if (context instanceof TomcatEmbeddedContext) {
			TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
			//添加Tomcat容器启动回调接口,来处理传入的initializers集合，从而完成Servlet，Filter，listener等Web组件的组注入
			embeddedContext.setStarter(starter);
			//用于控制在Servlet启动失败时是否标记整个Web应用程序上下文（Context）为失败状态,
			// 有一个或多个Servlet启动失败（例如，由于初始化过程中抛出了异常）,那么整个Web应用程序上下文将被标记为失败状态
			embeddedContext.setFailCtxIfServletStartFails(true);
		}
		// 同时TomcatStarter本身也实现了ServletContainerInitializer接口，将其添加到此context中的initializers集合中，
		// 再context组件start过程中或循环遍历该集合调用其onStartup方法
		context.addServletContainerInitializer(starter, NO_CLASSES);
		//将contextLifecycleListeners集合中的侦听器添加到context中，默认为null
		for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
			context.addLifecycleListener(lifecycleListener);
		}
		//往管道中设置value对象，默认为null
		for (Valve valve : this.contextValves) {
			context.getPipeline().addValve(valve);
		}
		//设置错误页面
		for (ErrorPage errorPage : getErrorPages()) {
			org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
			tomcatErrorPage.setLocation(errorPage.getPath());
			//状态码
			tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
			//异常类型
			tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
			context.addErrorPage(tomcatErrorPage);
		}
		//设置媒体类型(服务端返回某个数据浏览器如何处理)
		for (MimeMappings.Mapping mapping : getMimeMappings()) {
			context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
		}
		//配置context中的session和Cookie信息
		configureSession(context);
		configureCookieProcessor(context);
		new DisableReferenceClearingContextCustomizer().customize(context);
		//通过实现WebServerFactoryCustomizer接口，可以自定义一些Listener,添加到context的applicationListeners集合中
		//context组件在start过程中会循环遍历该集合（springMvc的父容器就是在该循环中初始化的，所以我们可以再该逻辑中通过自定的方式来添加新的listener） 默认为null
		for (String webListenerClassName : getWebListenerClassNames()) {
			context.addApplicationListener(webListenerClassName);
		}
		//该集合是在TomcatServletWebServerFactory bean对象实例化(ServletWebServerFactoryConfiguration)的时候通过属性注入进行的赋值
		for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
			customizer.customize(context);
		}
	}

	private void configureSession(Context context) {
		long sessionTimeout = getSessionTimeoutInMinutes();
		context.setSessionTimeout((int) sessionTimeout);
		Boolean httpOnly = getSession().getCookie().getHttpOnly();
		if (httpOnly != null) {
			context.setUseHttpOnly(httpOnly);
		}
		if (getSession().isPersistent()) {
			Manager manager = context.getManager();
			if (manager == null) {
				manager = new StandardManager();
				context.setManager(manager);
			}
			configurePersistSession(manager);
		}
		else {
			context.addLifecycleListener(new DisablePersistSessionListener());
		}
	}

	private void configureCookieProcessor(Context context) {
		SameSite sessionSameSite = getSession().getCookie().getSameSite();
		List<CookieSameSiteSupplier> suppliers = new ArrayList<>();
		if (sessionSameSite != null) {
			suppliers.add(CookieSameSiteSupplier.of(sessionSameSite)
					.whenHasName(() -> SessionConfig.getSessionCookieName(context)));
		}
		if (!CollectionUtils.isEmpty(getCookieSameSiteSuppliers())) {
			suppliers.addAll(getCookieSameSiteSuppliers());
		}
		if (!suppliers.isEmpty()) {
			context.setCookieProcessor(new SuppliedSameSiteCookieProcessor(suppliers));
		}
	}

	private void configurePersistSession(Manager manager) {
		Assert.state(manager instanceof StandardManager,
				() -> "Unable to persist HTTP session state using manager type " + manager.getClass().getName());
		File dir = getValidSessionStoreDir();
		File file = new File(dir, "SESSIONS.ser");
		((StandardManager) manager).setPathname(file.getAbsolutePath());
	}

	private long getSessionTimeoutInMinutes() {
		Duration sessionTimeout = getSession().getTimeout();
		if (isZeroOrLess(sessionTimeout)) {
			return 0;
		}
		return Math.max(sessionTimeout.toMinutes(), 1);
	}

	private boolean isZeroOrLess(Duration sessionTimeout) {
		return sessionTimeout == null || sessionTimeout.isNegative() || sessionTimeout.isZero();
	}

	/**
	 * Post process the Tomcat {@link Context} before it's used with the Tomcat Server.
	 * Subclasses can override this method to apply additional processing to the
	 * {@link Context}.
	 * @param context the Tomcat {@link Context}
	 */
	protected void postProcessContext(Context context) {
	}

	/**
	 * Factory method called to create the {@link TomcatWebServer}. Subclasses can
	 * override this method to return a different {@link TomcatWebServer} or apply
	 * additional processing to the Tomcat server.
	 * @param tomcat the Tomcat server.
	 * @return a new {@link TomcatWebServer} instance
	 */
	protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
		//将getWebServer中创建的Tomcat对象和当前类中port值是否大于等于0的判断结果作为TomcatWebServer构造方法的参数传入，创建TomcatWebServer对象。
		return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
	}
    //TomcatServletWebServerFactory实例化时调用ApplicationContextAwareProcessor后置处理器的postProcessBeforeInitialization方法
	// 将AnnotationConfigServletWebServerApplicationContext(ioc容器对象)作为参数传入
	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	@Override
	public void setBaseDirectory(File baseDirectory) {
		this.baseDirectory = baseDirectory;
	}

	/**
	 * Returns a mutable set of the patterns that match jars to ignore for TLD scanning.
	 * @return the list of jars to ignore for TLD scanning
	 */
	public Set<String> getTldSkipPatterns() {
		return this.tldSkipPatterns;
	}

	/**
	 * Set the patterns that match jars to ignore for TLD scanning. See Tomcat's
	 * catalina.properties for typical values. Defaults to a list drawn from that source.
	 * @param patterns the jar patterns to skip when scanning for TLDs etc
	 */
	public void setTldSkipPatterns(Collection<String> patterns) {
		Assert.notNull(patterns, "Patterns must not be null");
		this.tldSkipPatterns = new LinkedHashSet<>(patterns);
	}

	/**
	 * Add patterns that match jars to ignore for TLD scanning. See Tomcat's
	 * catalina.properties for typical values.
	 * @param patterns the additional jar patterns to skip when scanning for TLDs etc
	 */
	public void addTldSkipPatterns(String... patterns) {
		Assert.notNull(patterns, "Patterns must not be null");
		this.tldSkipPatterns.addAll(Arrays.asList(patterns));
	}

	/**
	 * The Tomcat protocol to use when create the {@link Connector}.
	 * @param protocol the protocol
	 * @see Connector#Connector(String)
	 */
	public void setProtocol(String protocol) {
		Assert.hasLength(protocol, "Protocol must not be empty");
		this.protocol = protocol;
	}

	/**
	 * Set {@link Valve}s that should be applied to the Tomcat {@link Engine}. Calling
	 * this method will replace any existing valves.
	 * @param engineValves the valves to set
	 */
	public void setEngineValves(Collection<? extends Valve> engineValves) {
		Assert.notNull(engineValves, "Valves must not be null");
		this.engineValves = new ArrayList<>(engineValves);
	}

	/**
	 * Returns a mutable collection of the {@link Valve}s that will be applied to the
	 * Tomcat {@link Engine}.
	 * @return the engine valves that will be applied
	 */
	public Collection<Valve> getEngineValves() {
		return this.engineValves;
	}

	@Override
	public void addEngineValves(Valve... engineValves) {
		Assert.notNull(engineValves, "Valves must not be null");
		this.engineValves.addAll(Arrays.asList(engineValves));
	}

	/**
	 * Set {@link Valve}s that should be applied to the Tomcat {@link Context}. Calling
	 * this method will replace any existing valves.
	 * @param contextValves the valves to set
	 */
	public void setContextValves(Collection<? extends Valve> contextValves) {
		Assert.notNull(contextValves, "Valves must not be null");
		this.contextValves = new ArrayList<>(contextValves);
	}

	/**
	 * Returns a mutable collection of the {@link Valve}s that will be applied to the
	 * Tomcat {@link Context}.
	 * @return the context valves that will be applied
	 * @see #getEngineValves()
	 */
	public Collection<Valve> getContextValves() {
		return this.contextValves;
	}

	/**
	 * Add {@link Valve}s that should be applied to the Tomcat {@link Context}.
	 * @param contextValves the valves to add
	 */
	public void addContextValves(Valve... contextValves) {
		Assert.notNull(contextValves, "Valves must not be null");
		this.contextValves.addAll(Arrays.asList(contextValves));
	}

	/**
	 * Set {@link LifecycleListener}s that should be applied to the Tomcat
	 * {@link Context}. Calling this method will replace any existing listeners.
	 * @param contextLifecycleListeners the listeners to set
	 */
	public void setContextLifecycleListeners(Collection<? extends LifecycleListener> contextLifecycleListeners) {
		Assert.notNull(contextLifecycleListeners, "ContextLifecycleListeners must not be null");
		this.contextLifecycleListeners = new ArrayList<>(contextLifecycleListeners);
	}

	/**
	 * Returns a mutable collection of the {@link LifecycleListener}s that will be applied
	 * to the Tomcat {@link Context}.
	 * @return the context lifecycle listeners that will be applied
	 */
	public Collection<LifecycleListener> getContextLifecycleListeners() {
		return this.contextLifecycleListeners;
	}

	/**
	 * Add {@link LifecycleListener}s that should be added to the Tomcat {@link Context}.
	 * @param contextLifecycleListeners the listeners to add
	 */
	public void addContextLifecycleListeners(LifecycleListener... contextLifecycleListeners) {
		Assert.notNull(contextLifecycleListeners, "ContextLifecycleListeners must not be null");
		this.contextLifecycleListeners.addAll(Arrays.asList(contextLifecycleListeners));
	}

	/**
	 * Set {@link TomcatContextCustomizer}s that should be applied to the Tomcat
	 * {@link Context}. Calling this method will replace any existing customizers.
	 * @param tomcatContextCustomizers the customizers to set
	 */
	public void setTomcatContextCustomizers(Collection<? extends TomcatContextCustomizer> tomcatContextCustomizers) {
		Assert.notNull(tomcatContextCustomizers, "TomcatContextCustomizers must not be null");
		this.tomcatContextCustomizers = new LinkedHashSet<>(tomcatContextCustomizers);
	}

	/**
	 * Returns a mutable collection of the {@link TomcatContextCustomizer}s that will be
	 * applied to the Tomcat {@link Context}.
	 * @return the listeners that will be applied
	 */
	public Collection<TomcatContextCustomizer> getTomcatContextCustomizers() {
		return this.tomcatContextCustomizers;
	}

	@Override
	public void addContextCustomizers(TomcatContextCustomizer... tomcatContextCustomizers) {
		Assert.notNull(tomcatContextCustomizers, "TomcatContextCustomizers must not be null");
		this.tomcatContextCustomizers.addAll(Arrays.asList(tomcatContextCustomizers));
	}

	/**
	 * Set {@link TomcatConnectorCustomizer}s that should be applied to the Tomcat
	 * {@link Connector}. Calling this method will replace any existing customizers.
	 * @param tomcatConnectorCustomizers the customizers to set
	 */
	public void setTomcatConnectorCustomizers(
			Collection<? extends TomcatConnectorCustomizer> tomcatConnectorCustomizers) {
		Assert.notNull(tomcatConnectorCustomizers, "TomcatConnectorCustomizers must not be null");
		this.tomcatConnectorCustomizers = new LinkedHashSet<>(tomcatConnectorCustomizers);
	}

	@Override
	public void addConnectorCustomizers(TomcatConnectorCustomizer... tomcatConnectorCustomizers) {
		Assert.notNull(tomcatConnectorCustomizers, "TomcatConnectorCustomizers must not be null");
		this.tomcatConnectorCustomizers.addAll(Arrays.asList(tomcatConnectorCustomizers));
	}

	/**
	 * Returns a mutable collection of the {@link TomcatConnectorCustomizer}s that will be
	 * applied to the Tomcat {@link Connector}.
	 * @return the customizers that will be applied
	 */
	public Collection<TomcatConnectorCustomizer> getTomcatConnectorCustomizers() {
		return this.tomcatConnectorCustomizers;
	}

	/**
	 * Set {@link TomcatProtocolHandlerCustomizer}s that should be applied to the Tomcat
	 * {@link Connector}. Calling this method will replace any existing customizers.
	 * @param tomcatProtocolHandlerCustomizer the customizers to set
	 * @since 2.2.0
	 */
	public void setTomcatProtocolHandlerCustomizers(
			Collection<? extends TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizer) {
		Assert.notNull(tomcatProtocolHandlerCustomizer, "TomcatProtocolHandlerCustomizers must not be null");
		this.tomcatProtocolHandlerCustomizers = new LinkedHashSet<>(tomcatProtocolHandlerCustomizer);
	}

	/**
	 * Add {@link TomcatProtocolHandlerCustomizer}s that should be added to the Tomcat
	 * {@link Connector}.
	 * @param tomcatProtocolHandlerCustomizers the customizers to add
	 * @since 2.2.0
	 */
	@Override
	public void addProtocolHandlerCustomizers(TomcatProtocolHandlerCustomizer<?>... tomcatProtocolHandlerCustomizers) {
		Assert.notNull(tomcatProtocolHandlerCustomizers, "TomcatProtocolHandlerCustomizers must not be null");
		this.tomcatProtocolHandlerCustomizers.addAll(Arrays.asList(tomcatProtocolHandlerCustomizers));
	}

	/**
	 * Returns a mutable collection of the {@link TomcatProtocolHandlerCustomizer}s that
	 * will be applied to the Tomcat {@link Connector}.
	 * @return the customizers that will be applied
	 * @since 2.2.0
	 */
	public Collection<TomcatProtocolHandlerCustomizer<?>> getTomcatProtocolHandlerCustomizers() {
		return this.tomcatProtocolHandlerCustomizers;
	}

	/**
	 * Add {@link Connector}s in addition to the default connector, e.g. for SSL or AJP
	 * @param connectors the connectors to add
	 */
	public void addAdditionalTomcatConnectors(Connector... connectors) {
		Assert.notNull(connectors, "Connectors must not be null");
		this.additionalTomcatConnectors.addAll(Arrays.asList(connectors));
	}

	/**
	 * Returns a mutable collection of the {@link Connector}s that will be added to the
	 * Tomcat.
	 * @return the additionalTomcatConnectors
	 */
	public List<Connector> getAdditionalTomcatConnectors() {
		return this.additionalTomcatConnectors;
	}

	@Override
	public void setUriEncoding(Charset uriEncoding) {
		this.uriEncoding = uriEncoding;
	}

	/**
	 * Returns the character encoding to use for URL decoding.
	 * @return the URI encoding
	 */
	public Charset getUriEncoding() {
		return this.uriEncoding;
	}

	@Override
	public void setBackgroundProcessorDelay(int delay) {
		this.backgroundProcessorDelay = delay;
	}

	/**
	 * Set whether the factory should disable Tomcat's MBean registry prior to creating
	 * the server.
	 * 设置工厂在创建之前是否应禁用Tomcat的MBean注册表服务器
	 * @param disableMBeanRegistry whether to disable the MBean registry
	 * @since 2.2.0
	 */
	public void setDisableMBeanRegistry(boolean disableMBeanRegistry) {
		this.disableMBeanRegistry = disableMBeanRegistry;
	}

	/**
	 * {@link LifecycleListener} to disable persistence in the {@link StandardManager}. A
	 * {@link LifecycleListener} is used so not to interfere with Tomcat's default manager
	 * creation logic.
	 */
	private static class DisablePersistSessionListener implements LifecycleListener {

		@Override
		public void lifecycleEvent(LifecycleEvent event) {
			if (event.getType().equals(Lifecycle.START_EVENT)) {
				Context context = (Context) event.getLifecycle();
				Manager manager = context.getManager();
				if (manager instanceof StandardManager) {
					((StandardManager) manager).setPathname(null);
				}
			}
		}

	}

	private final class StaticResourceConfigurer implements LifecycleListener {

		private final Context context;

		private StaticResourceConfigurer(Context context) {
			this.context = context;
		}

		@Override
		public void lifecycleEvent(LifecycleEvent event) {
			if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
				addResourceJars(getUrlsOfJarsWithMetaInfResources());
			}
		}

		private void addResourceJars(List<URL> resourceJarUrls) {
			for (URL url : resourceJarUrls) {
				String path = url.getPath();
				if (path.endsWith(".jar") || path.endsWith(".jar!/")) {
					String jar = url.toString();
					if (!jar.startsWith("jar:")) {
						// A jar file in the file system. Convert to Jar URL.
						jar = "jar:" + jar + "!/";
					}
					addResourceSet(jar);
				}
				else {
					addResourceSet(url.toString());
				}
			}
		}

		private void addResourceSet(String resource) {
			try {
				if (isInsideNestedJar(resource)) {
					// It's a nested jar but we now don't want the suffix because Tomcat
					// is going to try and locate it as a root URL (not the resource
					// inside it)
					resource = resource.substring(0, resource.length() - 2);
				}
				URL url = new URL(resource);
				String path = "/META-INF/resources";
				this.context.getResources().createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/", url, path);
			}
			catch (Exception ex) {
				// Ignore (probably not a directory)
			}
		}

		private boolean isInsideNestedJar(String dir) {
			return dir.indexOf("!/") < dir.lastIndexOf("!/");
		}

	}

	private static final class LoaderHidingResourceRoot extends StandardRoot {

		private LoaderHidingResourceRoot(TomcatEmbeddedContext context) {
			super(context);
		}

		@Override
		protected WebResourceSet createMainResourceSet() {
			return new LoaderHidingWebResourceSet(super.createMainResourceSet());
		}

	}

	private static final class LoaderHidingWebResourceSet extends AbstractResourceSet {

		private final WebResourceSet delegate;

		private final Method initInternal;

		private LoaderHidingWebResourceSet(WebResourceSet delegate) {
			this.delegate = delegate;
			try {
				this.initInternal = LifecycleBase.class.getDeclaredMethod("initInternal");
				this.initInternal.setAccessible(true);
			}
			catch (Exception ex) {
				throw new IllegalStateException(ex);
			}
		}

		@Override
		public WebResource getResource(String path) {
			if (path.startsWith("/org/springframework/boot")) {
				return new EmptyResource(getRoot(), path);
			}
			return this.delegate.getResource(path);
		}

		@Override
		public String[] list(String path) {
			return this.delegate.list(path);
		}

		@Override
		public Set<String> listWebAppPaths(String path) {
			return this.delegate.listWebAppPaths(path).stream()
					.filter((webAppPath) -> !webAppPath.startsWith("/org/springframework/boot"))
					.collect(Collectors.toSet());
		}

		@Override
		public boolean mkdir(String path) {
			return this.delegate.mkdir(path);
		}

		@Override
		public boolean write(String path, InputStream is, boolean overwrite) {
			return this.delegate.write(path, is, overwrite);
		}

		@Override
		public URL getBaseUrl() {
			return this.delegate.getBaseUrl();
		}

		@Override
		public void setReadOnly(boolean readOnly) {
			this.delegate.setReadOnly(readOnly);
		}

		@Override
		public boolean isReadOnly() {
			return this.delegate.isReadOnly();
		}

		@Override
		public void gc() {
			this.delegate.gc();
		}

		@Override
		protected void initInternal() throws LifecycleException {
			if (this.delegate instanceof LifecycleBase) {
				try {
					ReflectionUtils.invokeMethod(this.initInternal, this.delegate);
				}
				catch (Exception ex) {
					throw new LifecycleException(ex);
				}
			}
		}

	}

	/**
	 * {@link Rfc6265CookieProcessor} that supports {@link CookieSameSiteSupplier
	 * supplied} {@link SameSite} values.
	 */
	private static class SuppliedSameSiteCookieProcessor extends Rfc6265CookieProcessor {

		private final List<CookieSameSiteSupplier> suppliers;

		SuppliedSameSiteCookieProcessor(List<CookieSameSiteSupplier> suppliers) {
			this.suppliers = suppliers;
		}

		@Override
		public String generateHeader(Cookie cookie, HttpServletRequest request) {
			SameSite sameSite = getSameSite(cookie);
			if (sameSite == null) {
				return super.generateHeader(cookie, request);
			}
			Rfc6265CookieProcessor delegate = new Rfc6265CookieProcessor();
			delegate.setSameSiteCookies(sameSite.attributeValue());
			return delegate.generateHeader(cookie, request);
		}

		private SameSite getSameSite(Cookie cookie) {
			for (CookieSameSiteSupplier supplier : this.suppliers) {
				SameSite sameSite = supplier.getSameSite(cookie);
				if (sameSite != null) {
					return sameSite;
				}
			}
			return null;
		}

	}

}
